package free.spermwhales.gob;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

public class XmlParser {

	private File root;

	private DocumentBuilder builder;
	private XPath xPath;
	private Pattern regId;

	private List<File> dirs;
	private List<File> xmlsActuales;

	private Iterator<File> dirsIdx;
	private Iterator<File> xmlsIdx;

	private int lote;
	private int procesados;
	private int registrados;

	private boolean procesando;

	public XmlParser(String path, int lote) {
		try {
			this.builder = DocumentBuilderFactory.newInstance()
					.newDocumentBuilder();
			this.regId = Pattern.compile("[0-9]+");
			this.xPath = XPathFactory.newInstance().newXPath();
			this.lote = lote;
			this.procesados = 0;
			this.registrados = 0;
			this.procesando = false;

			registrarCarpetas(path);

		} catch (ParserConfigurationException e) {
			System.out.println(e.getStackTrace());
			System.exit(-1);
		}
	}

	public boolean getProcesando() {
		return procesando;
	}

	public void reset() {
		dirsIdx = null;
		xmlsIdx = null;
		xmlsActuales = null;
		procesados = 0;
		registrados = 0;
		procesando = false;
	}

	public int getProcesados() {
		return procesados;
	}

	public int getRegistrados() {
		return registrados;
	}

	public void setLote(int lote) {
		if (dirsIdx != null || xmlsIdx != null) {
			System.out
					.println("No puede cambiarse el maximo del lote hasta reiniciar el parser.");
		} else {
			this.lote = lote;
		}
	}

	public List<Registro> parse() {
		System.out.println("Comenzando a extraer los datos de los XMLs.");
		System.out.println("Extrayendo datos de los proximos " + lote
				+ " XMLs...");

		List<Registro> regs = new ArrayList<Registro>(lote);

		parse(regs);

		procesando = xmlsIdx.hasNext() || dirsIdx.hasNext();

		System.out
				.println("Retornando datos procesados de los XMLs. Total procesados: "
						+ procesados
						+ ". Total registrados:"
						+ registrados
						+ ". "
						+ (procesando ? "Aun quedan archivos por procesar."
								: "Extraccion de los datos finalizada."));

		if (!procesando) {
			reset();
		}

		return regs;
	}

	private int parse(Collection<Registro> regs) {
		int c = 0;

		boolean cont = xmlsIdx != null && dirsIdx != null;

		// si continua, termina el pucho y continua, sino inicializa el
		// iterador de directorios y arranca de 0
		if (cont) {
			c = parseXmlsIdx(c, regs);
		} else {
			dirsIdx = dirs.iterator();
		}

		while (dirsIdx.hasNext() && c < lote) {
			xmlsActuales = registrarXmls(dirsIdx.next());
			xmlsIdx = xmlsActuales.iterator();
			c = parseXmlsIdx(c, regs);
		}

		return c;
	}

	private List<File> registrarXmls(File dir) {

		System.out.println("Registrando XMLs de \"" + dir.getAbsolutePath()
				+ "\"...");

		List<File> xmls = new LinkedList<File>();

		for (File f : dir.listFiles()) {
			if (f.getName().toUpperCase().matches("^DOC[0-9]+.XML$")) {
				xmls.add(f);
			}
		}

		System.out.println("XMLs registrados!");
		return xmls;
	}

	private int parseXmlsIdx(int c, Collection<Registro> regs) {
		while (xmlsIdx.hasNext() && c < lote) {
			parseXml(xmlsIdx.next(), regs);
			c++;

			procesados++;
		}

		return c;
	}

	private void registrarCarpetas(String path) {
		System.out.println("Registrando directorios de \"" + path + "\"...");

		root = new File(path);

		try {
			if (!root.exists())
				throw new RuntimeException("No existe el directorio \"" + path
						+ "\". Saliendo.");

			if (!root.isDirectory())
				throw new RuntimeException("\"" + path
						+ "\" debe ser un directorio. Saliendo.");
		} catch (RuntimeException e) {
			e.getMessage();
			System.exit(-1);
		}

		dirs = new LinkedList<File>();

		agregarCarpetas(root, dirs);

		System.out.println("Directorios registrados!");
	}

	private void agregarCarpetas(File carp, List<File> carps) {

		System.out.println("Registrando \"" + carp.getAbsolutePath() + "\"");
		carps.add(carp);

		for (File f : carp.listFiles()) {
			if (f.isDirectory()) {
				agregarCarpetas(f, carps);
			}
		}
	}

	private Node getNodeFromDocument(String xpath, Document doc)
			throws XPathExpressionException {
		return (Node) xPath.compile(xpath).evaluate(doc, XPathConstants.NODE);
	}

	private Long findIdFromPath(String path) {
		Matcher mat = regId.matcher(path);
		mat.find();

		return Long.parseLong(mat.group());
	}

	private String nodeNotFound(String path, String node) {
		return "[WARN_STATE_NODE] " + path + ": No existe el nodo \"" + node
				+ "\".";
	}

	private String nodeAttNotFound(String path, String node, String att) {
		return "[WARN_STATE_ATT] " + path + ": El nodo " + node
				+ " no contiene al atributo \"" + att + "\".";
	}

	private String getNodeIdValue(String nodePath, String attName,
			String docPath, Document doc) {
		String val = null;

		try {
			Node node;

			node = getNodeFromDocument(nodePath, doc);

			if (node == null) {
				System.out.println(nodeNotFound(docPath, nodePath));
			} else {
				Node att = node.getAttributes().getNamedItem(attName);

				if (att == null) {
					System.out.println(nodeAttNotFound(docPath, nodePath,
							attName));
				} else {
					val = att.getNodeValue();
				}
			}
		} catch (XPathExpressionException e) {
			System.out.println(e.getStackTrace());
		}

		return val;
	}

	private void parseXml(File f, Collection<Registro> regs) {

		try {
			InputStream is = new FileInputStream(f);

			try {
				Document doc = builder.parse(is);

				Long docId = findIdFromPath(f.getName());

				String state = getNodeIdValue("document/properties/state",
						"id", f.getAbsolutePath(), doc);

				String status = getNodeIdValue("document/properties/status",
						"id", f.getAbsolutePath(), doc);

				if (state != null || status != null) {
					regs.add(new Registro(docId, state, status));
					registrados++;
				}
			} catch (IOException e) {
				System.out.println("No se puede acceder al archivo \""
						+ f.getAbsolutePath() + "\".");
			} catch (Exception e) {
				System.out
						.println("[ERR_PARSER] " + f.getAbsolutePath() + ": ");
				System.out.println(e.getStackTrace());
			}

			is.close();

		} catch (FileNotFoundException e) {
			System.out.println("No se encuentra al archivo \""
					+ f.getAbsolutePath() + "\".");
		} catch (IOException e) {
			System.out.println("Archivo \"" + f.getAbsolutePath()
					+ "\" inaccesible.");
		}
	}
}